package com.swak.vertx.config;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletionStage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;

import com.swak.Constants;
import com.swak.OS;
import com.swak.annotation.Context;
import com.swak.annotation.FluxService;
import com.swak.annotation.RestApi;
import com.swak.asm.MethodCache;
import com.swak.asm.MethodCache.ClassMeta;
import com.swak.asm.MethodCache.MethodMeta;
import com.swak.asm.Wrapper;
import com.swak.exception.AopWrapException;
import com.swak.exception.BusinessException;
import com.swak.exception.FluxInvokeException;
import com.swak.metrics.MethodMetrics;
import com.swak.metrics.MetricBinder;
import com.swak.metrics.MetricsFactory;
import com.swak.registry.URL;
import com.swak.utils.Lists;
import com.swak.utils.Maps;
import com.swak.utils.StringUtils;
import com.swak.vertx.transport.codec.Msg;
import com.swak.vertx.transport.script.Script;
import com.swak.vertx.transport.vertx.ServiceVerticle;

import io.vertx.core.Handler;
import io.vertx.core.eventbus.Message;
import lombok.EqualsAndHashCode;

/**
 * 创建服务 bean
 *
 * @author: lifeng
 * @date: 2020/3/29 18:52
 */
@EqualsAndHashCode(callSuper = false)
@SuppressWarnings("rawtypes")
public class ServiceBean extends AbstractBean implements Handler<Message<Msg>>, MetricBinder {

	private static Logger logger = LoggerFactory.getLogger(ServiceVerticle.class);

	private Object ref;
	private Class<?> beanClass;
	private Class<?> interClass;
	private Context context;
	private int instances;
	private String pool;
	private int workers;
	private ClassMeta classMeta;
	private String address;

	private Wrapper lazyWrapper;

	/**
	 * 发布的唯一ID
	 */
	protected String deploymentID;

	/**
	 * 指标监控缓存
	 */
	private Map<MethodMeta, MethodMetrics> metricsCache;

	@Override
	public void initializing() throws Exception {
		FluxService mapping = AnnotatedElementUtils.findMergedAnnotation(beanClass, FluxService.class);
		this.context = mapping.context();
		this.instances = mapping.parallel();
		this.pool = mapping.pool();
		this.workers = mapping.workers();
		// this.wrapper = Wrapper.getWrapper(this.interClass);
		this.classMeta = MethodCache.set(this.interClass);
		this.address = this.interClass.getName();
	}

	public Object getRef() {
		return ref;
	}

	public void setRef(Object ref) {
		this.ref = ref;
	}

	public Context getContext() {
		return context;
	}

	public int getInstances() {
		return instances;
	}

	public String getPool() {
		return pool;
	}

	public int getWorkers() {
		return workers;
	}

	public Class<?> getBeanClass() {
		return beanClass;
	}

	public void setBeanClass(Class<?> beanClass) {
		this.beanClass = beanClass;
	}

	public Class<?> getInterClass() {
		return interClass;
	}

	public void setInterClass(Class<?> interClass) {
		this.interClass = interClass;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getDeploymentID() {
		return deploymentID;
	}

	public ServiceBean setDeploymentID(String deploymentID) {
		this.deploymentID = deploymentID;
		return this;
	}

	private Wrapper createWrapper() {
		if (lazyWrapper == null) {
			synchronized (this) {
				if (lazyWrapper == null) {
					lazyWrapper = Wrapper.getWrapper(this.interClass);
				}
			}
		}
		return lazyWrapper;
	}

	/**
	 * 处理消息
	 */
	@Override
	public void handle(Message<Msg> event) {
		if (event.body().getScript() == null) {
			this.handleMethod(event);
		} else {
			this.handleScript(event);
		}
	}

	private void handleScript(Message<Msg> event) {
		Msg request = event.body();
		Script script = request.getScript();
		try {
			script.invoke();
			this.handleResult(null, null, null, null, event, "Script");
		} catch (Throwable e) {
			this.handleResult(null, e, null, null, event, "Script");
		}
	}

	@SuppressWarnings("unchecked")
	private void handleMethod(Message<Msg> event) {
		final Msg request = event.body();
		final MethodMeta method = this.classMeta.lookupQuietly(request.getMethodDesc());
		final MethodMetrics metrics = this.metricsCache != null && method != null ? this.metricsCache.get(method)
				: null;
		final Object metric = metrics != null ? metrics.begin() : null;
		try {
			Object result = this.createWrapper().invokeMethod(ref, request.getMethodDesc(), request.getArguments());
			if (result != null && result instanceof CompletionStage) {
				CompletionStage<Object> resultFuture = (CompletionStage<Object>) result;
				resultFuture.whenComplete(
						(r, e) -> this.handleResult(r, e, metrics, metric, event, request.getMethodDesc()));
			} else {
				this.handleResult(result, null, metrics, metric, event, request.getMethodDesc());
			}
		} catch (Throwable e) {
			this.handleResult(null, e, metrics, metric, event, request.getMethodDesc());
		}
	}

	@SuppressWarnings("unchecked")
	private void handleResult(Object result, Throwable e, MethodMetrics metrics, Object metric, Message<Msg> event,
			String method) {
		Msg request = event.body();
		Msg response = request.reset();
		if (e != null) {
			Throwable error = e.getCause() != null ? e.getCause() : e;

			/*
			 * 对Aop动态代理中抛出的异常进一步处理
			 */
			if (error instanceof AopWrapException) {
				error = error.getCause() != null ? error.getCause() : error;
			}

			/*
			 * 对应业务异常不需要打印
			 */
			if (!(error instanceof BusinessException)) {
				error = new FluxInvokeException(
						String.format("Failed to Invoke [Service:{%s} - Method: {%s}].", beanClass.getName(), method),
						e.getCause() != null ? e.getCause() : e);
				logger.error("响应式服务执行异常：", error);
			}
			response.setError(error);
		}
		if (result != null) {
			response.setResult(result);
		}
		event.reply(response);

		// 应用指标统计
		if (metrics != null) {
			metrics.end(metric, e == null);
		}
	}

	@Override
	public void bindTo(MetricsFactory metricsFactory) {
		RestApi api = AnnotatedElementUtils.findMergedAnnotation(beanClass, RestApi.class);
		if (api == null) {
			this.classMeta.getMethods().forEach((method) -> {
				this.applyMetrics(metricsFactory, method);
			});
		}
	}

	private void applyMetrics(MetricsFactory metricsFactory, MethodMeta method) {
		String metricName = method.getMethod().getDeclaringClass().getName() + "." + method.getMethodDesc();
		MethodMetrics metrics = metricsFactory.createMethodMetrics(method.getMethod(), metricName);
		if (metrics != null) {
			if (metricsCache == null) {
				metricsCache = Maps.newHashMap();
			}
			metricsCache.put(method, metrics);
		}
	}

	@Override
	public void export() {
		if (this.registryService != null) {
			List<String> methods = Lists.newArrayList();
			this.classMeta.getMethods().forEach((m) -> {
				if (!m.isLocal()) {
					methods.add(m.getMethodName());
				}
			});
			Map<String, String> parameters = Maps.newHashMap();
			parameters.put(Constants.Application_KEY, this.registryService.applicationName());
			parameters.put(Constants.Service_Key, this.interClass.getName());
			parameters.put(Constants.Category_Key, Constants.Registry_Flux_Service_Key);
			parameters.put(Constants.Registry_Flux_Service_Method_Key,
					StringUtils.join(methods, Constants.IDS_SEPARATE));
			this.url = new URL(Constants.Registry_Flux_Key, OS.ip(), 0, address, parameters);
		}
		super.export();
	}
}